/*
 * DefaultBoundedRangeModel.java  0.4.0 / Oct 26, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing;

import com.cute4j.core.events.EventListenerList;
import com.cute4j.swing.events.ChangeEvent;
import com.cute4j.swing.events.ChangeListener;

/**
 * Default Implementation of BoundedRangeModel.
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public class DefaultBoundedRangeModel implements BoundedRangeModel {

// Private Fields. -------------------------------------------------------------

  private int value;
  private int extent;
  private int minimum;
  private int maximum;
  private boolean adjusting;

// Protected Fields. -----------------------------------------------------------

  protected final ChangeEvent changeEvent;
  protected final EventListenerList listenerList;

// Public Constructors. --------------------------------------------------------

  /**
   * Constructs new DefaultBoundedRangeModel object.
   */
  public DefaultBoundedRangeModel() {
    maximum = 100;
    listenerList = new EventListenerList();
    changeEvent = new ChangeEvent(this);
  }

  /**
   * Constructs new DefaultBoundedRangeModel object.
   *
   * @param value the value
   * @param extent the extent
   * @param minimum the minimum
   * @param maximum the maximum
   */
  public DefaultBoundedRangeModel(int value, int extent, int minimum,
                                  int maximum) {
    if ((minimum > value) || (extent < 0) || ((value + extent) > maximum)) {
      throw new IllegalArgumentException("Invalid properties");
    }
    this.value = value;
    this.extent = extent;
    this.minimum = minimum;
    this.maximum = maximum;
    listenerList = new EventListenerList();
    changeEvent = new ChangeEvent(this);
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Returns change listeners.
   *
   * @return change listeners
   */
  public ChangeListener[] getChangeListeners() {
    int count = listenerList.getListenerCount(ChangeListener.class);
    ChangeListener[] listeners = new ChangeListener[count];
    if (count > 0) {
      System.arraycopy(listenerList.getListenersArray(ChangeListener.class), 0,
                       listeners,  0, count);
    }
    return listeners;
  }

// Protected Methods. ----------------------------------------------------------

  /**
   * Fires state changed event.
   */
  protected void fireStateChanged() {
    int count = listenerList.getListenerCount(ChangeListener.class);
    if (count > 0) {
      ChangeListener[] listeners = (ChangeListener[])listenerList.
                                       getListenersArray(ChangeListener.class);
      final ChangeEvent changeEvent = this.changeEvent;
      for (int i = count; --i >= 0;) {
        listeners[i].stateChanged(changeEvent);
      }
    }
  }

// BoundedRangeModel Interface Implementation. ---------------------------------

  /**
   * {@inheritDoc}
   */
  public int getValue() {
    return value;
  }

  /**
   * {@inheritDoc}
   */
  public void setValue(int value) { // NOPMD
    if (minimum > value) {
      value = minimum;
    }
    if ((value + extent) > maximum) {
      value = maximum - extent;
    }
    setRangeProperties(value, extent, minimum, maximum, adjusting);
  }

  /**
   * {@inheritDoc}
   */
  public int getMinimum() {
    return minimum;
  }

  /**
   * {@inheritDoc}
   */
  public void setMinimum(int minimum) {
    int maximum = this.maximum;
    int value = this.value;
    if (minimum > maximum) {
      maximum = minimum;
    }
    if (minimum > value) {
      value = minimum;
    }
    setRangeProperties(value, extent, minimum, maximum, adjusting);
  }

  /**
   * {@inheritDoc}
   */
  public int getMaximum() {
    return maximum;
  }

  /**
   * {@inheritDoc}
   */
  public void setMaximum(int maximum) {
    int minimum = this.minimum;
    int extent = this.extent;
    int value = this.value;
    if (maximum < minimum) {
      minimum = maximum;
    }
    int maxExtent = maximum - minimum;
    if (maxExtent < extent) {
      extent = maxExtent;
    }
    if ((value + extent) > maximum) {
      value = maximum - extent;
    }
    setRangeProperties(value, extent, minimum, maximum, adjusting);
  }

  /**
   * {@inheritDoc}
   */
  public int getExtent() {
    return extent;
  }

  /**
   * {@inheritDoc}
   */
  public void setExtent(int extent) { // NOPMD
    if (extent < 0) {
      extent = 0;
    }
    if ((value + extent) > maximum) {
      extent = maximum - value;
    }
    setRangeProperties(value, extent, minimum, maximum, adjusting);
  }

  /**
   * {@inheritDoc}
   */
  public boolean getValueIsAdjusting() {
    return adjusting;
  }

  /**
   * {@inheritDoc}
   */
  public void setValueIsAdjusting(boolean adjusting) {
    setRangeProperties(value, extent, minimum, maximum, adjusting);
  }

  /**
   * {@inheritDoc}
   */
  public void setRangeProperties(int value, int extent, int minimum, // NOPMD
                                 int maximum, boolean adjusting) { // NOPMD
    if (minimum > maximum) {
      minimum = maximum;
    }
    if (value > maximum) {
      maximum = value;
    }
    if (value < minimum) {
      minimum = value;
    }
    if ((extent + value) > maximum) {
      extent = maximum - value;
    }
    if (extent < 0) {
      extent = 0;
    }
    if ((value != this.value) || (extent != this.extent) ||
        (minimum != this.minimum) || (maximum != this.maximum) ||
        (adjusting != this.adjusting)) {
      this.value = value;
      this.extent = extent;
      this.minimum = minimum;
      this.maximum = maximum;
      this.adjusting = adjusting;
      fireStateChanged();
    }
  }

  /**
   * {@inheritDoc}
   */
  public void addChangeListener(ChangeListener listener) {
    listenerList.add(ChangeListener.class, listener);
  }

  /**
   * {@inheritDoc}
   */
  public void removeChangeListener(ChangeListener listener) {
    listenerList.remove(ChangeListener.class, listener);
  }

// Overridden Methods. ---------------------------------------------------------

  /**
   * {@inheritDoc}
   */
  public String toString() {
    return getClass().getName() + "[value=" + value + ", extent=" + extent +
           ", min=" + minimum + ", max=" + maximum + ", adj=" + adjusting + "]";
  }

// Inner Classes. --------------------------------------------------------------

  /**
   * Empty Model.
   */
  public static class EmptyModel implements BoundedRangeModel {
    public static final EmptyModel INSTANCE = new EmptyModel();
    public int getExtent() {
      return 0;
    }
    public int getMaximum() {
      return 0;
    }
    public int getMinimum() {
      return 0;
    }
    public int getValue() {
      return 0;
    }
    public boolean getValueIsAdjusting() {
      return false;
    }
    public void setExtent(int extent) {
      // empty method
    }
    public void setMaximum(int maximum) {
      // empty method
    }
    public void setMinimum(int minimum) {
      // empty method
    }
    public void setRangeProperties(int value, int extent, int minimum,
                                   int maximum, boolean adjusting) {
      // empty method
    }
    public void setValue(int value) {
      // empty method
    }
    public void setValueIsAdjusting(boolean adjusting) {
      // empty method
    }
    public void addChangeListener(ChangeListener listener) {
      // empty method
    }
    public void removeChangeListener(ChangeListener listener) {
      // empty method
    }
  }

}
